/* * Copyright 2016 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.agera.rvdatabinding; import static com.google.android.agera.Functions.identityFunction; import static com.google.android.agera.Functions.itemAsList; import static com.google.android.agera.Functions.resultAsList; import static com.google.android.agera.Functions.resultListAsList; import static com.google.android.agera.Functions.staticFunction; import static com.google.android.agera.Preconditions.checkNotNull; import static com.google.android.agera.rvdatabinding.RecycleConfig.CLEAR_COLLECTION; import static com.google.android.agera.rvdatabinding.RecycleConfig.CLEAR_HANDLERS; import static com.google.android.agera.rvdatabinding.RecycleConfig.CLEAR_ITEM; import static com.google.android.agera.rvdatabinding.RecycleConfig.DO_NOTHING; import static java.util.Collections.emptyList; import android.databinding.DataBindingUtil; import android.databinding.ViewDataBinding; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v7.util.DiffUtil; import android.support.v7.util.ListUpdateCallback; import android.support.v7.widget.RecyclerView; import android.util.SparseArray; import android.view.View; import com.google.android.agera.Function; import com.google.android.agera.Result; import com.google.android.agera.rvadapter.RepositoryPresenter; import com.google.android.agera.rvadapter.RepositoryPresenterCompilerStates.RPItemCompile; import com.google.android.agera.rvadapter.RepositoryPresenterCompilerStates.RPLayout; import com.google.android.agera.rvdatabinding.DataBindingRepositoryPresenterCompilerStates.DBRPMain; import java.lang.ref.WeakReference; import java.util.List; @SuppressWarnings("unchecked") final class DataBindingRepositoryPresenterCompiler implements DBRPMain, RPLayout, RPItemCompile { @NonNull private static final Function<Object, Object> NO_KEY_FOR_ITEM = identityFunction(); @NonNull private static final Function<Object, Object> SAME_KEY_FOR_ITEM = staticFunction(new Object()); private static final int BR_NO_ID = -1; @NonNull private final SparseArray<Object> handlers; private Function<Object, Integer> layoutFactory; private Function<Object, Integer> itemId = staticFunction(BR_NO_ID); private int collectionId = BR_NO_ID; @NonNull private Function<Object, Long> stableIdForItem = staticFunction(RecyclerView.NO_ID); @RecycleConfig private int recycleConfig = DO_NOTHING; @NonNull private Function<Object, Object> keyForItem = NO_KEY_FOR_ITEM; private boolean detectMoves; DataBindingRepositoryPresenterCompiler() { this.handlers = new SparseArray<>(); } @NonNull @Override public DBRPMain handler(final int handlerId, @NonNull final Object handler) { handlers.put(handlerId, handler); return this; } @NonNull @Override public DBRPMain itemId(final int itemId) { this.itemId = staticFunction(itemId); return this; } @NonNull @Override public DBRPMain itemIdForItem(@NonNull final Function itemIdForItem) { this.itemId = checkNotNull(itemIdForItem); return this; } @NonNull @Override public DBRPMain diffWith(@NonNull final Function keyForItem, final boolean detectMoves) { this.keyForItem = keyForItem; this.detectMoves = detectMoves; return this; } @NonNull @Override public RPItemCompile diff() { this.keyForItem = SAME_KEY_FOR_ITEM; this.detectMoves = false; return this; } @NonNull @Override public RepositoryPresenter forItem() { return new CompiledRepositoryPresenter(itemId, layoutFactory, stableIdForItem, handlers, recycleConfig, itemAsList(), collectionId, keyForItem, detectMoves); } @NonNull @Override public RepositoryPresenter<List<Object>> forList() { return new CompiledRepositoryPresenter(itemId, layoutFactory, stableIdForItem, handlers, recycleConfig, (Function) identityFunction(), collectionId, keyForItem, detectMoves); } @NonNull @Override public RepositoryPresenter<Result<Object>> forResult() { return new CompiledRepositoryPresenter(itemId, layoutFactory, stableIdForItem, handlers, recycleConfig, (Function) resultAsList(), collectionId, keyForItem, detectMoves); } @NonNull @Override public RepositoryPresenter<Result<List<Object>>> forResultList() { return new CompiledRepositoryPresenter(itemId, layoutFactory, stableIdForItem, handlers, recycleConfig, (Function) resultListAsList(), collectionId, keyForItem, detectMoves); } @NonNull @Override public RepositoryPresenter forCollection(@NonNull final Function converter) { return new CompiledRepositoryPresenter(itemId, layoutFactory, stableIdForItem, handlers, recycleConfig, converter, collectionId, keyForItem, detectMoves); } @NonNull @Override public Object layout(@LayoutRes int layoutId) { this.layoutFactory = staticFunction(layoutId); return this; } @NonNull @Override public Object layoutForItem(@NonNull final Function layoutForItem) { this.layoutFactory = checkNotNull(layoutForItem); return this; } @NonNull @Override public DBRPMain stableIdForItem(@NonNull final Function stableIdForItem) { this.stableIdForItem = stableIdForItem; return this; } @NonNull @Override public RPItemCompile stableId(final long stableId) { this.stableIdForItem = staticFunction(stableId); return this; } @NonNull @Override public DBRPMain onRecycle(@RecycleConfig final int recycleConfig) { this.recycleConfig = recycleConfig; return this; } @NonNull @Override public DBRPMain collectionId(final int collectionId) { this.collectionId = collectionId; return this; } private static final class CompiledRepositoryPresenter extends RepositoryPresenter { @NonNull private final Function<Object, Integer> itemId; @NonNull private final Function<Object, List<Object>> converter; @NonNull private final Function<Object, Integer> layoutId; @NonNull private final Function<Object, Long> stableIdForItem; @RecycleConfig private final int recycleConfig; private final int collectionId; @NonNull private final SparseArray<Object> handlers; private final boolean enableDiff; @NonNull private final Function<Object, Object> keyForItem; private final boolean detectMoves; @NonNull private WeakReference<Object> dataRef = new WeakReference<>(null); @NonNull private List items = emptyList(); CompiledRepositoryPresenter( @NonNull final Function<Object, Integer> itemId, @NonNull final Function<Object, Integer> layoutId, @NonNull final Function<Object, Long> stableIdForItem, @NonNull final SparseArray<Object> handlers, final int recycleConfig, @NonNull final Function<Object, List<Object>> converter, final int collectionId, @NonNull final Function<Object, Object> keyForItem, final boolean detectMoves) { this.itemId = itemId; this.collectionId = collectionId; this.converter = converter; this.layoutId = layoutId; this.stableIdForItem = stableIdForItem; this.recycleConfig = recycleConfig; this.handlers = handlers; this.enableDiff = keyForItem != NO_KEY_FOR_ITEM; this.keyForItem = keyForItem; this.detectMoves = detectMoves; } @Override public int getItemCount(@NonNull final Object data) { return getItems(data).size(); } @Override public int getLayoutResId(@NonNull final Object data, final int index) { return layoutId.apply(getItems(data).get(index)); } @Override public void bind(@NonNull final Object data, final int index, @NonNull final RecyclerView.ViewHolder holder) { final Object item = getItems(data).get(index); final View view = holder.itemView; final ViewDataBinding viewDataBinding = DataBindingUtil.bind(view); final Integer itemVariable = itemId.apply(item); if (itemVariable != BR_NO_ID) { viewDataBinding.setVariable(itemVariable, item); view.setTag(R.id.agera__rvdatabinding__item_id, itemVariable); } if (collectionId != BR_NO_ID) { viewDataBinding.setVariable(collectionId, data); view.setTag(R.id.agera__rvdatabinding__collection_id, collectionId); } for (int i = 0; i < handlers.size(); i++) { final int variableId = handlers.keyAt(i); viewDataBinding.setVariable(variableId, handlers.valueAt(i)); } viewDataBinding.executePendingBindings(); } @Override public void recycle(@NonNull final RecyclerView.ViewHolder holder) { if (recycleConfig != 0) { final View view = holder.itemView; final ViewDataBinding viewDataBinding = DataBindingUtil.bind(view); if ((recycleConfig & CLEAR_ITEM) != 0) { final Object tag = view.getTag(R.id.agera__rvdatabinding__item_id); view.setTag(R.id.agera__rvdatabinding__item_id, null); if (tag instanceof Integer) { viewDataBinding.setVariable((int) tag, null); } } if ((recycleConfig & CLEAR_COLLECTION) != 0) { final Object collectionTag = view.getTag(R.id.agera__rvdatabinding__collection_id); view.setTag(R.id.agera__rvdatabinding__collection_id, null); if (collectionTag instanceof Integer) { viewDataBinding.setVariable((int) collectionTag, null); } } if ((recycleConfig & CLEAR_HANDLERS) != 0) { for (int i = 0; i < handlers.size(); i++) { viewDataBinding.setVariable(handlers.keyAt(i), null); } } viewDataBinding.executePendingBindings(); } } @Override public long getItemId(@NonNull final Object data, final int index) { return stableIdForItem.apply(getItems(data).get(index)); } @NonNull private List getItems(@NonNull final Object data) { if (this.dataRef.get() != data) { items = converter.apply(data); this.dataRef = new WeakReference<>(data); } return items; } @Override public boolean getUpdates(@NonNull final Object oldData, @NonNull final Object newData, @NonNull final ListUpdateCallback listUpdateCallback) { if (!enableDiff) { return false; } if (oldData == newData) { // Consider this an additional observable update; send blanket change event final int itemCount = getItemCount(oldData); listUpdateCallback.onChanged(0, itemCount, null); return true; } // Do proper diffing. final List oldItems = getItems(oldData); final List newItems = getItems(newData); // This conveniently saves newData to dataRef. DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { return oldItems.size(); } @Override public int getNewListSize() { return newItems.size(); } @Override public boolean areItemsTheSame(final int oldItemPosition, final int newItemPosition) { final Object oldKey = keyForItem.apply(oldItems.get(oldItemPosition)); final Object newKey = keyForItem.apply(newItems.get(newItemPosition)); return oldKey.equals(newKey); } @Override public boolean areContentsTheSame(final int oldItemPosition, final int newItemPosition) { final Object oldItem = oldItems.get(oldItemPosition); final Object newItem = newItems.get(newItemPosition); return oldItem.equals(newItem); } }, detectMoves).dispatchUpdatesTo(listUpdateCallback); return true; } } }